﻿using System.Drawing;
using System.Security;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Windows.Forms.VisualStyles;

namespace System.Custom.Drawing
{
   [StructLayout(LayoutKind.Sequential)]
   internal struct MARGINS
   {
      public int cxLeftWidth;      // width of left border that retains its size
      public int cxRightWidth;     // width of right border that retains its size
      public int cyTopHeight;      // height of top border that retains its size
      public int cyBottomHeight;   // height of bottom border that retains its size

      public MARGINS(int all)
      {
         cxLeftWidth = cxRightWidth = cyTopHeight = cyBottomHeight = all < 0 ? 0 : all;
      }
      public MARGINS(bool fullWindow)
      {
         cxLeftWidth = cxRightWidth = cyTopHeight = cyBottomHeight = (fullWindow ? -1 : 0);
      }
   };

   [StructLayout(LayoutKind.Sequential)]
   internal struct DWM_BLURBEHIND
   {
      public DwmBlurBehindDwFlags dwFlags;
      public bool fEnable;
      public IntPtr hRgnBlur;
      public bool fTransitionOnMaximized;
   };

   internal struct POINTAPI
   {
      public int x;
      public int y;
   };

   internal struct RECT
   {
      public int left;
      public int top;
      public int right;
      public int bottom;
   };

   internal struct RGBQUAD
   {
      public byte rgbBlue;
      public byte rgbGreen;
      public byte rgbRed;
      public byte rgbReserved;
   };

   internal struct BITMAPINFO
   {
      public BITMAPINFOHEADER bmiHeader;
      public RGBQUAD bmiColors;
   };

   internal struct DTTOPTS
   {
      public uint dwSize;
      public uint dwFlags;
      public uint crText;
      public uint crBorder;
      public uint crShadow;
      public int iTextShadowType;
      public POINTAPI ptShadowOffset;
      public int iBorderSize;
      public int iFontPropId;
      public int iColorPropId;
      public int iStateId;
      public int fApplyOverlay;
      public int iGlowSize;
      public IntPtr pfnDrawTextCallback;
      public int lParam;
   };

   internal struct BITMAPINFOHEADER
   {
      public int biSize;
      public int biWidth;
      public int biHeight;
      public short biPlanes;
      public short biBitCount;
      public int biCompression;
      public int biSizeImage;
      public int biXPelsPerMeter;
      public int biYPelsPerMeter;
      public int biClrUsed;
      public int biClrImportant;
   };

   internal enum CompositionEnable : uint
   {
      DWM_EC_DISABLECOMPOSITION = 0,
      DWM_EC_ENABLECOMPOSITION = 1
   }

   internal static class DWMMessages
   {
      internal const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
      internal const int WM_DWMNCRENDERINGCHANGED = 0x031F;
   }

   internal enum DwmBlurBehindDwFlags : uint
   {
      DWM_BB_ENABLE = 0x00000001,
      DWM_BB_BLURREGION = 0x00000002,
      DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004
   }

   [SuppressUnmanagedCodeSecurity]
   public static class AeroGlass
   {
      #region Data

      private const int BI_RGB = 0;
      private const int SRCCOPY = 0x00CC0020;
      private const int DIB_RGB_COLORS = 0x000000;

      private const int DTT_COMPOSITED = (int)(1UL << 13);
      private const int DTT_GLOWSIZE = (int)(1UL << 11);

      #endregion

      public static bool CompositionEnabled
      {
         get
         {
            return Environment.OSVersion.Version.Major >= 6 && DwmIsCompositionEnabled();
         }
      }

      public static void FillBlack(Graphics gph, Rectangle rgn)
      {
         RECT rc = new RECT();
         rc.left = rgn.Left;
         rc.right = rgn.Right;
         rc.top = rgn.Top;
         rc.bottom = rgn.Bottom;

         IntPtr destdc = gph.GetHdc();    //hwnd must be the handle of form,not control
         IntPtr Memdc = CreateCompatibleDC(destdc);
         IntPtr bitmap;
         IntPtr bitmapOld = IntPtr.Zero;

         BITMAPINFO dib = new BITMAPINFO();
         dib.bmiHeader.biHeight = -(rc.bottom - rc.top);
         dib.bmiHeader.biWidth = rc.right - rc.left;
         dib.bmiHeader.biPlanes = 1;
         dib.bmiHeader.biSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
         dib.bmiHeader.biBitCount = 32;
         dib.bmiHeader.biCompression = BI_RGB;

         if (!(SaveDC(Memdc) == 0))
         {
            bitmap = CreateDIBSection(Memdc, ref dib, DIB_RGB_COLORS, 0, IntPtr.Zero, 0);
            if (!(bitmap == IntPtr.Zero))
            {
               bitmapOld = SelectObject(Memdc, bitmap);
               BitBlt(destdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, Memdc, 0, 0, SRCCOPY);

            }
            //Remember to clean up
            SelectObject(Memdc, bitmapOld);

            DeleteObject(bitmap);

            ReleaseDC(Memdc, -1);
            DeleteDC(Memdc);
         }
         gph.ReleaseHdc();
      }

      public static void ApplyEffect(Form form, bool enabled, Control excludeControl)
      {
         if (!CompositionEnabled || form.Handle == IntPtr.Zero) return;

         if (excludeControl == null)
         {
            MARGINS margins = enabled ? new MARGINS(true) : new MARGINS(0);

            DwmExtendFrameIntoClientArea(form.Handle, ref margins);
         }
         else
         {
            Rectangle formRect = form.RectangleToScreen(form.ClientRectangle);
            Rectangle controlRect = excludeControl.RectangleToScreen(excludeControl.ClientRectangle);

            MARGINS margins = new MARGINS();
            margins.cxLeftWidth = controlRect.Left - formRect.Left;
            margins.cxRightWidth = formRect.Right - controlRect.Right;
            margins.cyTopHeight = controlRect.Top - formRect.Top;
            margins.cyBottomHeight = formRect.Bottom - controlRect.Bottom;

            // Extend the Frame into client area
            DwmExtendFrameIntoClientArea(form.Handle, ref margins);
         }
      }

      public static void DrawText(Form form, String text, Font font, Rectangle ctlrct, int iglowSize, TextFormatFlags format)
      {
         if (CompositionEnabled)
         {
            RECT rc = new RECT();
            RECT rc2 = new RECT();

            rc.left = ctlrct.Left;
            rc.right = ctlrct.Right + 2 * iglowSize;  //make it larger to contain the glow effect
            rc.top = ctlrct.Top;
            rc.bottom = ctlrct.Bottom + 2 * iglowSize;

            //Just the same rect with rc,but (0,0) at the lefttop
            rc2.left = 0;
            rc2.top = 0;
            rc2.right = rc.right - rc.left;
            rc2.bottom = rc.bottom - rc.top;

            IntPtr destdc = GetDC(form.Handle);
            IntPtr memdc = CreateCompatibleDC(destdc);   // Set up a memory DC where we'll draw the text.
            IntPtr bitmap;
            IntPtr bitmapOld = IntPtr.Zero;
            IntPtr logfontOld;

            BITMAPINFO dib = new BITMAPINFO();
            dib.bmiHeader.biHeight = -(rc.bottom - rc.top);         // negative because DrawThemeTextEx() uses a top-down DIB
            dib.bmiHeader.biWidth = rc.right - rc.left;
            dib.bmiHeader.biPlanes = 1;
            dib.bmiHeader.biSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
            dib.bmiHeader.biBitCount = 32;
            dib.bmiHeader.biCompression = BI_RGB;

            if (SaveDC(memdc) != 0)
            {
               bitmap = CreateDIBSection(memdc, ref dib, DIB_RGB_COLORS, 0, IntPtr.Zero, 0);   // Create a 32-bit bmp for use in offscreen drawing when glass is on

               if (bitmap != IntPtr.Zero)
               {
                  bitmapOld = SelectObject(memdc, bitmap);
                  IntPtr hFont = font.ToHfont();
                  logfontOld = SelectObject(memdc, hFont);

                  try
                  {
                     VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.Window.Caption.Active);

                     DTTOPTS dttOpts = new DTTOPTS();

                     dttOpts.dwSize = (uint)Marshal.SizeOf(typeof(DTTOPTS));

                     dttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;

                     dttOpts.iGlowSize = iglowSize;

                     DrawThemeTextEx(renderer.Handle, memdc, 0, 0, text, -1, (int)format, ref rc2, ref dttOpts);

                     BitBlt(destdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, memdc, 0, 0, SRCCOPY);
                  }
                  catch (Exception e)
                  {
                     //Trace.WriteLine(e.Message);
                  }

                  //Remember to clean up
                  SelectObject(memdc, bitmapOld);
                  SelectObject(memdc, logfontOld);
                  DeleteObject(bitmap);
                  DeleteObject(hFont);

                  ReleaseDC(memdc, -1);
                  DeleteDC(memdc);
               }
            }
         }
      }

      #region API imports

      [DllImport("DwmApi.dll")]
      internal static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND bb);

      [DllImport("DwmApi.dll")]
      internal static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS m);

      [DllImport("DwmApi.dll", PreserveSig = false)]
      internal static extern bool DwmIsCompositionEnabled();

      [DllImport("DwmApi.dll")]
      internal static extern int DwmEnableComposition(CompositionEnable compositionAction);

      [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern IntPtr GetDC(IntPtr hdc);

      [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern int SaveDC(IntPtr hdc);

      [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern int ReleaseDC(IntPtr hdc, int state);

      [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern IntPtr CreateCompatibleDC(IntPtr hDC);

      [DllImport("gdi32.dll", ExactSpelling = true)]
      internal static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

      [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern bool DeleteObject(IntPtr hObject);

      [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern bool DeleteDC(IntPtr hdc);

      [DllImport("gdi32.dll")]
      internal static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

      [DllImport("UxTheme.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
      internal static extern int DrawThemeTextEx(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, string text, int iCharCount, int dwFlags, ref RECT pRect, ref DTTOPTS pOptions);

      [DllImport("UxTheme.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern int DrawThemeText(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, string text, int iCharCount, int dwFlags1, int dwFlags2, ref RECT pRect);

      [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO pbmi, uint iUsage, int ppvBits, IntPtr hSection, uint dwOffset);

      #endregion
   }
}